package pers.cz.jefconfig.reader.properties;

import pers.cz.jefconfig.reader.Reader;

import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Map;

/**
 * @program: JefConfig
 * @description: properties文件解析器
 * @author: Cheng Zhi
 * @create: 2023-04-10 09:30
 **/
public class PropertiesReader implements Reader {

    @Override
    public void readLine(FileInputStream fileInputStream, Map<String, Object> map) throws IOException {
        try (FileChannel channel = fileInputStream.getChannel();) {
            //ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            // 处理半包问题（读取文件较大，byteBuferr不能够一次性装载，需要分多次，此时将出现半包问题）,因此先读取文件大小，然后再申请内存
            int size = fileInputStream.available();
            ByteBuffer byteBuffer = ByteBuffer.allocateDirect(size); // 使用系统内存效率更高一些，零拷贝，但是内存分配可能比较慢,jvm之外的内存回收如果不去主动释放，就要依赖jvm gc去触发
            // 读取文件名
            while (true) {
                // 按照字节来读取
                int read = channel.read(byteBuffer);
                if (read <= 0) {
                    break;
                }
                byteBuffer.flip();
                readLine(byteBuffer, map);
                byteBuffer.clear();
            }

            byteBuffer = null; // 帮忙jvm垃圾回收器通知调用freeMemory方法来释放系统内存
        }

    }

    public void readLine(ByteBuffer byteBuffer, Map<String, Object> map) {
        // 是否为注释行
        boolean isCommendLine = false;
        // 是否为新行
        boolean isNewLine = false;
        // 是否时key后面的第一个有效字符
        boolean isAfterKey = false;
        Character splitKey = null;
        String key = "";
        String value = "";

        StringBuilder sb = new StringBuilder();
        while(byteBuffer.hasRemaining()) {
            char c = (char) byteBuffer.get();

            if (!key.equals("") && !value.equals("")) {
                map.put(key,value);
                key = "";
                value = "";
                splitKey = null;
                isAfterKey = false;
            }

            // asdfghjk\nadfadfadf\nerer
            if (c == '\n' || c == '\r') {
                isNewLine = true;
                isCommendLine = false; // 如果遇到换行则重新判断是否为注释行
            } else {
                isNewLine = false;
            }
            // 判断是否找到value的有效字符
            if (splitKey != null && sb.length()>0) {
                isAfterKey = true;
            }
            // 如果第一个字符是空格，tab制表符则跳过
            if (c == ' ' || c == '\t') {
                //continue;
                /**
                 * 这里要判断是不是分割符后的第一个空格或者分割符后的连续空格，如 key    =     insert into test_tabel xxxxx
                 * 当遇到分割符后的第一个字符后，之后的空格应当保留
                 */
                if (!isAfterKey) {
                    continue;
                }
            }

            // 如果去除了空格之外是#或者！则表示注释行,注释行之后的内容不处理，直到遇到换行或者回车
            if (c == '#' || c == '!') {
                isCommendLine = true;
            }

            // 判断如果是新行，看是不是为空则不处理
            if (isNewLine) {
                // 如果是新行，且字符串长度为0,则不处理
                int length = sb.length();
                if (length == 0) {
                    continue;
                }
                isCommendLine = false;
                isNewLine = false;
                value = sb.toString();
                sb = new StringBuilder();
                continue;
            }
            // 如果已经找到key,并且找到valued的有效字符，则后面不需要进行任务判断，认为分隔符后面的有效字符之后的都是有用信息。原封不动拼接
            if (isAfterKey) {
                sb.append(c);
                continue;
            }
            if (!isCommendLine) {
                // 如果遇到 = ： 则认为要分割,前面的为key, 后面的为value, 如果前面已经有了=则后面的：不应该再继续拆分，反之亦然,当找到有效字符后，后面的字符，则不在检验各种格式
                if (c == '=' || c == ':') {
                    if (splitKey == null) {
                        splitKey = c;
                    }

                }

                if (splitKey != null && c == splitKey) {
                    key = sb.toString();
                    // 重置sb
                    sb = new StringBuilder();
                    continue;
                }
                sb.append(c);
            }

        }
    }

}
